#include <iostream>
#include <memory>
#include <vector>
#include <string>
#include <string>
#include <fstream>
#include <streambuf>
#include <sstream>
#include <cstdlib>
#include <new>
#include <typeinfo>
#include <list>
#include <thread> // std::thread
#include <mutex>  // std::mutex

using namespace std;

// 参考: https://blog.csdn.net/misayaaaaa/article/details/53786215
// 参考: https://github.com/applenob/Cpp_Primer_Practice/blob/master/excersize/ch15.md
// chapter 15 面向对象程序设计


// 1.
// 什么是虚成员？
// 对于某些函数，基类希望它的派生类各自定义适合自身的版本，此时基类就将这些函数声明成虚函数

面向对象程序设计的三个基本概念：数据抽象、继承和动态绑定(核心概念)

数据抽象：将类的接口与实现分离

继承：我们可以定义与其他类相似但完全不相同的新类

动态绑定：在使用这些彼此相似的类时，在一定程度上忽略他们的区别，统一使用它们的对象

知识点2：类的继承构成一种层次关系，在层次根部的为基类，其他类则直接或者间接的从基类中继承而来，称为派生类

基类负责定义在层次关系中所有类共有的数据成员，而派生类定义各自特有的成员

知识点3：对于某些函数，基类希望它的派生类各自定义适合其自身的版本，基类会将该函数声明为虚函数！！！而派生类必须在其内部对所有重新定义的虚函数进行声明，若不加virtual关键字，可以在其后加上override显式声明改写基类的虚函数

知识点4：派生类必须通过使用类派生列表明确指出它是从哪个基类中继承而来的：冒号+（访问限定符）基类列表

知识点5：动态绑定，函数的运行版本由实参决定也被称为运行时绑定：在使用基类的引用调用一个虚函数时将会发生动态绑定！！！当我们使用指针或者引用调用虚函数时，将调用动态绑定

知识点6：基类通常应该定义一个虚析构函数，即使该函数不执行任何操作也是如此！

知识点7：关键词virtual只能出现在类内的声明函数语句之前，而不能用于类外部的函数定义，在派生类中相应的函数将隐式的是虚函数（不加virtual的情况）

知识点8：非虚函数，其解析过程将发生在编译时而非运行时

知识点9：protected受保护的成员，基类希望它的派生类有权访问该成员，同时禁止其他用户访问，而private即使是其派生类也不能访问


// 2. protected 访问说明符与 private 有何区别？
// protected: 基类和和其派生类还有友元可以访问。
// private: 只有基类本身和友元可以访问


// 3.



// 4.
// 下面哪条声明语句是不正确的？请解释原因。

// class Base { ... };
// (a) class Derived : public Derived { ... };
// (b) class Derived : private Base { ... };
// (c) class Derived : public Base;
// 解：

// (a) 不正确。类不能派生自身。
// (b) 不正确。这是定义而非声明。
// (c) 不正确。派生列表不能出现在这



// 5.



// 6.



// 7.



// 8. 给出静态类型和动态类型的定义
// 表达式的静态类型在编译时总是已知的，它是变量声明时的类型或表达式生成的类型。
// 动态类型则是变量或表达式表示的内存中的对象的类型。动态类型直到运行时才可知



// 9. 在什么情况下表达式的静态类型可能与动态类型不同？请给出三个静态类型与动态类型不同的例子
// 基类的指针或引用的静态类型可能与其动态类型不一致


知识点1：通常情况下，如果我们想使用指针或者引用绑定一个对象，则指针或者引用的类型需要和对象的类型一致或者可进行const转换，但是存在继承关系的类是一个重要的例外：我们可以将基类的指针或者引用绑定到派生类的对象上：这就意味着，当我们使用基类的指针或者引用时，我们并不知道该指针或引用所绑定的对象的真实类型，该对象可能是基类的对象，也可能是派生类的对象

知识点2：当我们使用一个变量或者表达式时，我们需要将其静态类型和动态类型相互区分开，表达式的静态类型是在编译时已知的，是变量声明时的类型或者表达式生成的类型，其动态类型是变量或者表达式表示内存的对象的类型，知道运行时才可知，即如item对象，静态类型为Quote&，动态类型则依赖于item所绑定的实参，直到函数运行时才可知

知识点3：如果表达式既不是指针也不是引用，则其动态类型和静态类型会一直绑定在一起


// 10.

关键点：存在继承关系的类型之间的转换规则

1：从派生类像基类类型转换只有对指针或者引用类型有效

2：基类到派生类不存在隐式类型转换

3：派生类到基类的类型转换也可能会由于访问限制而变得不可行



// 11.

// 12.
// 有必要将一个成员函数同时声明成 override 和 final 吗？为什么？
// 有必要。override 的含义是重写基类中相同名称的虚函数，final 是阻止它的派生类重写当前虚函数
// 当然有需要啊，两个不一样的，override是表示对其基类函数的覆盖，final是表示此函数不能再被其他的函数所覆盖



// 13.
class base {
public:
	string name() { return basename;}
	virtual void print(ostream &os) { os << basename; }
private:
	string basename;
};
class derived : public base {
public:
    // 错误
    void print(ostream &os) { print(os); os << " " << i; } // 错误
    // 加上作用域限定符
    void print(ostream &os) override { base::print(os); os << " derived\n " << i; }
private:
	int i;
};


在知识点6中，我们提到了成员函数的函数体中避免虚函数的问题，因为这会导致函数的自我调用，导致无限循环，此处派生类的print()函数中使用的print()函数会自动被解析为对自身的调用。所以应加上作用域限定符：

base::print(os);



// 14.
// 给定上一题中的类以及下面这些对象，说明在运行时调用哪个函数：

base bobj; 		base *bp1 = &bobj; 	base &br1 = bobj;
derived dobj; 	base *bp2 = &dobj; 	base &br2 = dobj;
(a) bobj.print();	 // 调用基类版本的print()函数
(b) dobj.print();	 // 调用派生类版本的print()函数
(c) bp1->name();  // 调用基类的name()函数
(d) bp2->name();  // 调用派生类中基类部分的name()函数
(e) br1.print();  // 调用基类版本
(f) br2.print();  // 调用派生类版本




// 15.




// 16.


// 17.



// 18.
// 只有在派生类是使用public的方式继承基类时，用户代码才可以使用派生类到基类（derived-to-base）的转换。
假设给定了第543页和第544页的类，同时已知每个对象的类型如注释所示，判断下面的哪些赋值语句是合法的。解释那些不合法的语句为什么不被允许：

Base *p = &d1;  //d1 的类型是 Pub_Derv
p = &d2;		//d2 的类型是 Priv_Derv
p = &d3;		//d3 的类型是 Prot_Derv
p = &dd1;		//dd1 的类型是 Derived_from_Public	
p = &dd2;		//dd2 的类型是 Derived_from_Private
p = &dd3;		//dd3 的类型是 Derived_from_Protected
解：

Base *p = &d1; 合法
p = &d2; 不合法
p = &d3; 不合法
p = &dd1; 合法
p = &dd2; 不合法
p = &dd3; 不合法


// 19.




// 20.
class  Base
{
public:
	void pub_mem();
protected:
	int pro_mem();
private:
	char pri_mem();
};
 
struct Pub_derv:public Base 
{
	int f(){return pro_mem();}
 
	//错误，私有成员不可访问
	char g(){return pri_mem();}
};
struct Pri_derv:private Base 
{
	int f1(){return pro_mem();}
 
	//错误，私有成员也不可访问
	char g1(){return pri_mem();}
};
struct Pro_derv:protected Base 
{
	int f2(){return pro_mem();}
 
	//错误，私有成员也不可访问
	char g3(){return pri_mem();}
};
 
struct Derivated_from_Public:public Pub_derv
{
	int use(){ return pro_mem();}
};
struct Derivated_from_Protected:public Pro_derv
{
	int use(){ return pro_mem();}
};
struct Derivated_from_Private:public Pri_derv
{
	//错误，不可访问
	int use(){ return pro_mem();}
};


    Pub_derv d1;
	Pri_derv d2;
	Pro_derv d3;
	Derivated_from_Public dd1;
	Derivated_from_Protected dd2;
	Derivated_from_Private dd3;
	d1.pub_mem();
/*	d2.pub_mem();//错误，不可访问，该对象成员都为私有*/
	Base *p = &d1;
	p = &d2;//不允许对不可访问的基类进行转换
	p = &d3;//不允许对不可访问的基类进行转换
	p = &dd1;
	p = &dd2;//不允许对不可访问的基类进行转换
	p = &dd3;//不允许对不可访问的基类进行转换





// 21.



// 22.



// 23.





// 24.
// 哪种类需要虚析构函数？虚析构函数必须执行什么样的操作？
// 基类通常应该定义一个虚析构函数
知识点1：虚析构函数：可以动态分配继承体系中的对象，如果我们需要删除一个指向派生类对象的基类指针，就需要虚析构函数，这样可以确保delete基类指针时将运行正确的虚构函数版本(动态绑定虚析构函数)

知识点2：基类需要一个虚析构函数产生的影响：一个类定义了析构函数，即使它通过=default的形式生成合成的版本，编译器也不会为这个类合成移动操作



答案：基类需要虚析构函数，需要动态销毁对象



// 25. 我们为什么为 Disc_quote 定义一个默认构造函数？如果去掉该构造函数的话会对 Bulk_quote 的行为产生什么影响？
// 如果基类中的默认构造函数、拷贝构造函数或是析构函数被删除或是不可访问，则其派生类中的相应成员是被删除的
// 如果删除了Quote基类的默认构造函数，那么它所有派生类中都不能使用默认构造函数进行初始化对象，如果需要默认初始化，则会报错


知识点1：如果基类中的默认构造函数、拷贝构造函数或是析构函数被删除或是不可访问，则其派生类中的相应成员是被删除的

知识点2：大多数基类都会有一个虚析构函数，因此基类通常不会含有合成的移动操作，派生类中也如此，如果我们确实需要移动的操作，我们需要自行首先在基类中进行定义



如果删除了Quote基类的默认构造函数，那么它所有派生类中都不能使用默认构造函数进行初始化对象，如果需要默认初始化，则会报错

// 26.



// 27.




// 28.





// 29.





// 30.



// 31. 已知 s1、s2、s3 和 s4 都是 string，判断下面的表达式分别创建了什么样的对象：

(a) Query(s1) | Query(s2) & ~Query(s3);
(b) Query(s1) | (Query(s2) & ~Query(s3));
(c) (Query(s1) & (Query(s2)) | (Query(s3) & Query(s4)));
解：

(a) OrQuery, AndQuery, NotQuery, WordQuery
(b) OrQuery, AndQuery, NotQuery, WordQuery
(c) OrQuery, AndQuery, WordQuery
知识点1：继承体系：is  a ,和has  a 的区别。

知识点2：将层次关系隐藏与接口类之中，保存一个基类（抽象基类）派生类的对象的指针




// 32. 当一个 Query 类型的对象被拷贝、移动、赋值或销毁时，将分别发生什么？

解：

拷贝：当被拷贝时，合成的拷贝构造函数被调用。它将拷贝两个数据成员至新的对象。而在这种情况下，数据成员是一个智能指针，当拷贝时，相应的智能指针指向相同的地址，计数器增加1.
移动：当移动时，合成的移动构造函数被调用。它将移动数据成员至新的对象。这时新对象的智能指针将会指向原对象的地址，而原对象的智能指针为 nullptr，新对象的智能指针的引用计数为 1。
赋值：合成的赋值运算符被调用，结果和拷贝的相同的。
销毁：合成的析构函数被调用。对象的智能指针的引用计数递减，当引用计数为 0 时，对象被销毁。

拷贝：调用合成的拷贝构造函数，将智能指针q的内容复制到新对象的对应成员，使智能指针q计数+1
移动：调用合成的移动构造函数，将智能指针q的内容移动到新对象的对应成员，使智能指针q指向NULL
赋值：调用合成的拷贝赋值运算符，将智能指针q的内容替换为参数对象的对应成员的内容，使q计数-1，并让参数计数+1
摧毁：调用合成的析构函数，清理自身，并调用q的析构函数，使计数-1




// 33. 当一个 Query_base 类型的对象被拷贝、移动赋值或销毁时，将分别发生什么？

解：

由合成的版本来控制。然而 Query_base 是一个抽象类，它的对象实际上是它的派生类对象。

由于没有数据成员，所以合成的拷贝控制足以应付所有问题


// 34.




// 35.




// 36.


// 38. 下面的声明合法吗？如果不合法，请解释原因;如果合法，请指出该声明的含义。

BinaryQuery a = Query("fiery") & Query("bird");
AndQuery b = Query("fiery") & Query("bird");
OrQuery c = Query("fiery") & Query("bird");
解：

不合法。因为 BinaryQuery 是抽象类。
不合法。& 操作返回的是一个 Query 对象。
不合法。& 操作返回的是一个 Query 对象。




// 39.


int main()
{

   
    // 结束   chapter 16 模板和范型编程

    printf("hello c++ \n");
}